package org.chipu.jnids.sce.profile;

import static org.chipu.jnids.sce.profile.StatType.Type.*;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.*;
import java.util.Map.Entry;
import org.chipu.jnids.beans.Profile;
import org.chipu.jnids.beans.Snapshot;

/** This is the class that carries out the actual comparation */
public class AnormalStateEngine {
	private static AnormalStateEngine INSTANCE;
	// private final Collection<Profile> blackProfiles = new HashSet<Profile>();
	private final Collection<Profile> whiteProfiles = new HashSet<Profile>(2);
	private final List<Method> testMethods;

	private AnormalStateEngine() {
		// TODOLATER manage several profiles
		testMethods = new ArrayList<Method>();
		for(Method m : AnormalStateEngine.class.getDeclaredMethods())
			if (m.getAnnotation(AnormalCheck.class) != null)
				testMethods.add(m);
	}

	public static List<AnormalMessage> getAnormalState(Snapshot snapshot) {
		if (INSTANCE == null)
			INSTANCE = new AnormalStateEngine();
		List<AnormalMessage> anormality = new ArrayList<AnormalMessage>();

		// TODO blackProfiles

		for(Profile p : INSTANCE.whiteProfiles) {
			Collection<AnormalMessage> msgs = INSTANCE.correspondCurrentStateToProfile(snapshot, p);
			if (msgs != null)
				anormality.addAll(msgs);
		}
		return anormality;
	}

	/** @return null if all the test are inside the limits for the profile */
	private Collection<AnormalMessage> correspondCurrentStateToProfile(Snapshot snapshot, Profile p) {
		Set<AnormalMessage> msgs = new HashSet<AnormalMessage>();
		for(Method m : testMethods)
			try {
				AnormalMessage msg = (AnormalMessage) m.invoke(this, snapshot, p);
				if (msg != null)
					msgs.add(msg);
			} catch(Exception ex) {
				ex.printStackTrace();
				// TODO add predefined AnormalMessage
			}
		return msgs;
	}

	// -----

	public static Anormality getAnormalities(Stats reference, Stats current) {
		return new Anormality(current.average / reference.average - 1, current.standardDeviation / reference.standardDeviation
			- 1, current.skewness / reference.skewness - 1, current.kurtosis / reference.kurtosis - 1);
	}

	// -----

	/** Detects many to one situations */
	@AnormalCheck
	private AnormalMessage testDDOS() {
		return null;
	}

	@AnormalCheck
	private AnormalMessage testDOS() {
		return null;
	}

	/** Detect several blind attacks against the same machine */
	@AnormalCheck
	public AnormalMessage testTestAndFail() {
		return null;
	}

	/** Detects one to many the same technique (changing hosts) */
	@AnormalCheck
	public AnormalMessage testExploration() {
		return null;
	}

	/** Detects one to one on the same technique (changing ports) */
	@AnormalCheck
	private AnormalMessage testExploration2() {
		return null;
	}

	// -----Connections

	@AnormalCheck
	private AnormalMessage testIngoingGlobalConnections(Snapshot snapshot, Profile p) {
		StatType st = StatType.getStatType(StatType.Type.CONNECTIONS_IN);
		Anormality a = getAnormalities(p.getRelatedStats(st), snapshot.getStats(st));
		return a.isNormal()? null: new AnormalMessage(a, st);
	}

	@AnormalCheck
	private AnormalMessage testOutgoingGlobalConnections(Snapshot snapshot, Profile p) {
		StatType st = StatType.getStatType(StatType.Type.CONNECTIONS_OUT);
		Anormality a = getAnormalities(p.getRelatedStats(st), snapshot.getStats(st));
		return a.isNormal()? null: new AnormalMessage(a, st);
	}

	private static AnormalMessage tesHostConnections(Snapshot snapshot, Profile p, StatType.Type type) {
		StatType st = StatType.getStatType(type);
		for(Entry<InetAddress, Integer> e : snapshot.getMostActiveHosts(st).entrySet()) {
			StatType st2 = new StatType(st.type, e.getKey());
			if (p.isMostActiveHost(st2)) {
				// we compare the current host activity with the profile host activity
				Anormality a = getAnormalities(p.getRelatedStats(st2), snapshot.getStats(st2));
				if (!a.isNormal())
					return new AnormalMessage(a, st);
			} else {
				// we compare the current host activity with the profile averageHostAcivity
				Anormality a = getAnormalities(p.getRelatedStats(st), snapshot.getStats(st2));
				if (!a.isNormal())
					return new AnormalMessage(a, st);
			}
		}
		// TODO notify ALL the anormalities
		return null;
	}

	@AnormalCheck
	private AnormalMessage testIngoingInternalHostConnections(Snapshot snapshot, Profile p) {
		return tesHostConnections(snapshot, p, CONNECTIONS_TO_INTERNAL);
	}

	@AnormalCheck
	private AnormalMessage testOutgoingInternalHostConnections(Snapshot snapshot, Profile p) {
		return tesHostConnections(snapshot, p, CONNECTIONS_FROM_INTERNAL);
	}

	/** Attack from a host */
	@AnormalCheck
	private AnormalMessage testIngoingExternalHostConnections(Snapshot snapshot, Profile p) {
		return tesHostConnections(snapshot, p, CONNECTIONS_FROM_EXTERNAL);
	}

	/** Getting info */
	@AnormalCheck
	private AnormalMessage testOutgoingExternalHostConnections(Snapshot snapshot, Profile p) {
		return tesHostConnections(snapshot, p, CONNECTIONS_TO_EXTERNAL);
	}
}